package cc.rengu.igas.mcps.core.service.base;

import cc.rengu.igas.mcps.common.constant.McpsParamConstant;
import cc.rengu.igas.mcps.common.constant.McpsTreeNodeConstant;
import cc.rengu.igas.mcps.common.dao.TermKeyMapper;
import cc.rengu.igas.mcps.common.dao.impl.TermKeyMapperImpl;
import cc.rengu.igas.mcps.common.entity.TermKey;
import cc.rengu.igas.mcps.common.enums.KeyTypeEnum;
import cc.rengu.igas.mcps.common.enums.RespCodeEnum;
import cc.rengu.igas.mcps.core.model.AcctAndFeeType;
import cc.rengu.igas.mcps.core.realize.PosHsmService;
import cc.rengu.igas.mcps.core.realize.PosParamDealService;
import cc.rengu.igas.mcps.core.realize.PosPublicCheckService;
import cc.rengu.igas.mcps.core.realize.impl.PosHsmServiceImpl;
import cc.rengu.igas.mcps.core.realize.impl.PosParamDealServiceImpl;
import cc.rengu.igas.mcps.core.realize.impl.PosPublicCheckServiceImpl;
import cc.rengu.igas.share.common.constant.TransCacheConstant;
import cc.rengu.igas.share.core.model.MchntFeeInfo;
import cc.rengu.igas.share.core.model.MchntInfo;
import cc.rengu.igas.share.core.realize.MchntService;
import cc.rengu.igas.share.core.realize.impl.MchntServiceImpl;
import cc.rengu.igas.share.core.realize.impl.RiskMonitorServiceImpl;
import cc.rengu.oltp.service.base.BizBaseService;
import cc.rengu.oltp.service.common.constant.AppParamConstant;
import cc.rengu.oltp.service.common.constant.TreeNodeConstant;
import cc.rengu.oltp.service.common.enums.AcctTypeEnum;
import cc.rengu.oltp.service.common.enums.ChannelEnum;
import cc.rengu.oltp.service.common.enums.LimitAcctTypeEnum;
import cc.rengu.oltp.service.model.BizException;
import cc.rengu.oltp.service.model.BizResponse;
import cc.rengu.oltp.service.model.RiskMonitorInfo;
import cc.rengu.oltp.service.realize.RiskMonitorService;
import cc.rengu.oltp.utility.annotation.BIZ_AFTER;
import cc.rengu.oltp.utility.annotation.BIZ_BEFORE;
import cc.rengu.oltp.utility.annotation.BIZ_PROCESS;
import cc.rengu.oltp.utility.util.*;
import com.alibaba.fastjson.JSON;

import java.util.List;
import java.util.Optional;

/**
 * 线下支付服务基类
 * Created by zhangbin on 2018/4/17.
 */
public abstract class PayService extends BizBaseService {

    @Override
    protected Object beforeBizProcess() throws Exception {
        XmlTreeUtil xmlTreeUtil = new XmlTreeUtil();
        String mchntNo = xmlTreeUtil.getXmlTreeStringValue(McpsTreeNodeConstant.MCHNT_NO);
        String termNo = xmlTreeUtil.getXmlTreeStringValue(McpsTreeNodeConstant.TERM_NO);
        String track2Data = xmlTreeUtil.getXmlTreeStringValue(McpsTreeNodeConstant.TRACK2DATA);
        String track3Data = xmlTreeUtil.getXmlTreeStringValue(McpsTreeNodeConstant.TRACK3DATA);
        String instId = xmlTreeUtil.getXmlTreeStringValue(TreeNodeConstant.INST_ID);
        String payAcctNo = xmlTreeUtil.getXmlTreeStringValue(McpsTreeNodeConstant.PAN);
        String channelId = xmlTreeUtil.getXmlTreeStringValue(TreeNodeConstant.TRANS_CHANNEL_ID);
        String pinData = xmlTreeUtil.getXmlTreeStringValue(McpsTreeNodeConstant.PIN_DATA);
        String transAmt = xmlTreeUtil.getXmlTreeStringValue(McpsTreeNodeConstant.TRANS_AMT);

        /* 金额不允许出现负数金额及0金额 */
        if (!StringUtil.isEmptyOrNull(transAmt) && AmountUtil.compare(transAmt, "0") <= 0) {
            rglog.error("金额不合法,{}", transAmt);
            throw new BizException(RespCodeEnum.REQUEST_ERROR.getRespCode(), RespCodeEnum.REQUEST_ERROR.getRespDesc());
        }
        xmlTreeUtil.setXmlTreeStringValue(TreeNodeConstant.ORDER_AMT, transAmt);

        /* Step1：查找BIZ_BEFORE的注解*/
        invokeBizAnnoMethod(BIZ_BEFORE.class);

        /* Step2：金融类交易公共校验 */
        PosPublicCheckService posPublicCheckService = new PosPublicCheckServiceImpl();
        if (McpsParamConstant.REQ_NPOS_ID.equals(channelId)) {
            /* Step2.1：mac验签，地理位置校验，sn校验，签到校验 */
            posPublicCheckService.posPublicCheckService();

            /* Step3：敏感信息解密 */
            PosHsmService posHsmService = new PosHsmServiceImpl();
            /* Step3.1：获取终端工作密钥 */
            TermKeyMapper termKeyMapper = new TermKeyMapperImpl();
            TermKey tdkTermkey = termKeyMapper.getTermKey(instId, mchntNo, termNo, KeyTypeEnum.TDK.getTypeCode());
            if (tdkTermkey == null) {
                rglog.error("未获取到敏感信息加密密钥，拒绝交易");
                throw new BizException(RespCodeEnum.MCHNT_APP_SECRTT_NOT_FOUND.getRespCode(), RespCodeEnum.MCHNT_APP_SECRTT_NOT_FOUND.getRespDesc());
            }
            /* Step3.2：支付账号解密 */
            AcctAndFeeType acctAndFeeType;
            if (!StringUtil.isEmptyOrNull(payAcctNo)) {
                payAcctNo = posHsmService.descriptData(tdkTermkey, payAcctNo);
                if (payAcctNo == null || payAcctNo.length() > 36) {
                    rglog.error("解密数据失败，拒绝交易");
                    xmlTreeUtil.setXmlTreeStringValue(McpsTreeNodeConstant.PAN, "");
                    throw new BizException(RespCodeEnum.MCHNT_APP_SECRTT_NOT_FOUND.getRespCode(), RespCodeEnum.MCHNT_APP_SECRTT_NOT_FOUND.getRespDesc());
                }
                xmlTreeUtil.setXmlTreeStringValue(McpsTreeNodeConstant.PAN, payAcctNo);
                /* Step3.2.1：获取卡号对应的账户类型及费率类型 */
                PosParamDealService posParamDealService = new PosParamDealServiceImpl();
                acctAndFeeType = posParamDealService.judgeOwnOtherBank(instId, payAcctNo, txnInfo.getSrcTxnAuthCfg().getSupportAcctType());
                xmlTreeUtil.setXmlTreeObjectValue(McpsTreeNodeConstant.ACCT_AND_FEE_TYPE, acctAndFeeType);
            } else {
                rglog.error("卡号未上送");
                throw new BizException(RespCodeEnum.REQUEST_ERROR.getRespCode(), RespCodeEnum.REQUEST_ERROR.getRespDesc());
            }
            /* Step3.3：二磁解密 */
            if (!StringUtil.isEmptyOrNull(track2Data)) {
                track2Data = posHsmService.descriptData(tdkTermkey, track2Data);
                if (track2Data == null) {
                    rglog.error("解密数据失败，拒绝交易");
                    throw new BizException(RespCodeEnum.MCHNT_APP_SECRTT_NOT_FOUND.getRespCode(), RespCodeEnum.MCHNT_APP_SECRTT_NOT_FOUND.getRespDesc());
                }
                xmlTreeUtil.setXmlTreeStringValue(McpsTreeNodeConstant.TRACK2DATA, track2Data);
            }
            /* Step3.4：三磁解密 */
            if (!StringUtil.isEmptyOrNull(track3Data)) {
                track3Data = posHsmService.descriptData(tdkTermkey, track3Data);
                if (track3Data == null) {
                    rglog.error("解密数据失败，拒绝交易");
                    throw new BizException(RespCodeEnum.MCHNT_APP_SECRTT_NOT_FOUND.getRespCode(), RespCodeEnum.MCHNT_APP_SECRTT_NOT_FOUND.getRespDesc());
                }
                xmlTreeUtil.setXmlTreeStringValue(McpsTreeNodeConstant.TRACK3DATA, track3Data);
            }

            /* Step4：密码转PIN */
            if (!StringUtil.isEmptyOrNull(pinData) && null != acctAndFeeType) {
                String dstChannelId = ChannelEnum.CUPS.getChannelType();
                if (AcctTypeEnum.BANK_DEBIT.getAcctType().equals(acctAndFeeType.getAcctType()) || AcctTypeEnum.BANK_CREDIT.getAcctType().equals(acctAndFeeType.getFeeType())) {
                    dstChannelId = ChannelEnum.ESB.getChannelType();
                }
                String newPin = posHsmService.transPin(instId, mchntNo, termNo, payAcctNo, dstChannelId, pinData);
                if (null == newPin) {
                    rglog.error("转pin错，拒绝交易");
                    throw new BizException(RespCodeEnum.POS_PIN_FORMAT_ERR.getRespCode(), RespCodeEnum.POS_PIN_FORMAT_ERR.getRespDesc());
                }
                xmlTreeUtil.setXmlTreeStringValue(McpsTreeNodeConstant.PIN_DATA, newPin);
            }
        }

        /* Step5：商户和终端的业务权限校验 */
        posPublicCheckService.posPublicAuthCtrlCheckService(txnInfo);

        /* Step6：交易处理前客户化 */
        return bizBeforeCust();
    }

    @Override
    protected BizResponse callBizService(Object request) throws Exception {
        XmlTreeUtil xmlTreeUtil = new XmlTreeUtil();
        String mchntNo = xmlTreeUtil.getXmlTreeStringValue(McpsTreeNodeConstant.MCHNT_NO);
        String orderAmt = xmlTreeUtil.getXmlTreeStringValue(McpsTreeNodeConstant.ORDER_AMT);
        String instId = xmlTreeUtil.getXmlTreeStringValue(McpsTreeNodeConstant.INST_ID);
        String pinData = xmlTreeUtil.getXmlTreeStringValue(McpsTreeNodeConstant.PIN_DATA);
        String posEntryModeCode = xmlTreeUtil.getXmlTreeStringValue(McpsTreeNodeConstant.POS_ENTRY_MODE_CODE);
        xmlTreeUtil.copyStringValue(McpsTreeNodeConstant.PAN, "encPayAcctNo");

        /* Step1：判断商户等级:小微商户不支持磁条卡交易 */
        MchntService mchntService = new MchntServiceImpl();
        MchntInfo mchntInfo = mchntService.getMchntInfo(instId, mchntNo);
        if (null == mchntInfo) {
            rglog.error("查询商户信息失败<{}>", mchntNo);
            throw new BizException(RespCodeEnum.MCHNT_INFO_NOT_FOUND.getRespCode(), RespCodeEnum.MCHNT_INFO_NOT_FOUND.getRespDesc());
        }
        if ("20".equals(mchntInfo.getMchntType()) && posEntryModeCode.startsWith("02")) {
            rglog.error("小微商户不支持磁条卡交易!!,服务点输入方式码：<{}>,商户号：<{}>", posEntryModeCode, mchntNo);
            throw new BizException(RespCodeEnum.WX_MCHNT_NOT_SUPPORT.getRespCode(), RespCodeEnum.WX_MCHNT_NOT_SUPPORT.getRespDesc());
        }

        /* Step2：免密校验：判断交易金额是否大于上送金额 */
        String noPasswordFlag = mchntInfo.getNoPasswordFlag();
        String noPasswordAmt = mchntInfo.getNoPasswordAmt();
        if ("Y".equals(noPasswordFlag) && !StringUtil.isEmptyOrNull(noPasswordAmt) && StringUtil.isEmptyOrNull(pinData)) {
            if (AmountUtil.compare(noPasswordAmt, orderAmt) < 0) {
                rglog.error("交易金额大于小额免签免密设定金额");
                throw new BizException(RespCodeEnum.AMOUNT_OUT_CONTROL.getRespCode(), RespCodeEnum.AMOUNT_OUT_CONTROL.getRespDesc());
            }
        }

        /* Step3：交易限额检查 */
        AcctAndFeeType acctAndFeeType = (AcctAndFeeType) xmlTreeUtil.getXmlTreeObjectValue(McpsTreeNodeConstant.ACCT_AND_FEE_TYPE, AcctAndFeeType.class);
        if (null != acctAndFeeType) {
            /*检查限额控制*/
            int returnCode;
            if (AcctTypeEnum.BANK_DEBIT.getAcctType().equals(acctAndFeeType.getAcctType()) || AcctTypeEnum.OTHER_DEBIT.getAcctType().equals(acctAndFeeType.getAcctType())) {
                returnCode = mchntService.checkMchntTransLimit(instId, orderAmt, LimitAcctTypeEnum.DEBIT, mchntNo, null, txnInfo.getSrcTxnAuthCfg().getTxnNum());
            } else {
                returnCode = mchntService.checkMchntTransLimit(instId, orderAmt, LimitAcctTypeEnum.CREDIT, mchntNo, null, txnInfo.getSrcTxnAuthCfg().getTxnNum());
            }
            if (0 != returnCode) {
                rglog.error("商户<{}>交易金额超限!", mchntNo);
                throw new BizException(RespCodeEnum.MCC_LIMIT_AMT_OVER.getRespCode(), RespCodeEnum.MCC_LIMIT_AMT_OVER.getRespDesc());
            }
        }

        /* Step4：获取商户的费率信息:非独立清算获取一级商户费率信息 */
        if (AppParamConstant.NO.equals(mchntInfo.getIndependentDataFlag()) && "11".equals(mchntInfo.getMchntType())) {
            mchntNo = mchntInfo.getChainMchntNo();
        }
        List<MchntFeeInfo> mchntFeeInfoList = mchntService.getMchntFeeInfo(instId, mchntNo);
        if (null == mchntFeeInfoList || mchntFeeInfoList.isEmpty()) {
            rglog.error("获取商户<{}>扣率失败!", mchntNo);
            throw new BizException(RespCodeEnum.MCHNT_FREE_NOT_EXIST.getRespCode(), RespCodeEnum.MCHNT_FREE_NOT_EXIST.getRespDesc());
        }
        Optional<MchntFeeInfo> mchntFeeInfoOpt = mchntFeeInfoList.stream().filter(item -> item.getFeeInputMode().equals("0")).findFirst();
        if (!"04".equals(txnInfo.getSrcTxnAuthCfg().getBizProdCode())) {
            MchntFeeInfo mchntFeeInfo = mchntFeeInfoList.get(0);
            /* Step4.1：非分期交易判断是否为特殊费率商户 */
            if (mchntFeeInfoOpt.isPresent()) {
                mchntFeeInfo = mchntFeeInfoOpt.get();
            } else {
                String bizProdCode = txnInfo.getSrcTxnAuthCfg().getBizProdCode();
                String currentDate = DateUtil.getCurrentDate();
                mchntFeeInfoOpt = mchntFeeInfoList.stream().filter(item -> item.getBaseProdCode().equals(bizProdCode) && null != acctAndFeeType &&
                        item.getFeeType().equals(acctAndFeeType.getFeeType()) && item.getFeeEffectiveDate().compareTo(currentDate) <= 0 &&
                        item.getFeeExpiryDate().compareTo(DateUtil.getCurrentDate()) >= 0).findFirst();
                if (mchntFeeInfoOpt.isPresent()) {
                    mchntFeeInfo = mchntFeeInfoOpt.get();
                }
            }
            rglog.info("商户付费率信息:<{}>", JSON.toJSONString(mchntFeeInfo));
            GlobalTransUtil.setCacheValue(TransCacheConstant.MCHNT_FEE_INFO, mchntFeeInfo);
        } else {
            /* Step4.2：分期交易设置特殊费率商户标志 */
            if (!mchntFeeInfoOpt.isPresent()) {
                xmlTreeUtil.setXmlTreeStringValue(McpsTreeNodeConstant.SPECIAL_FEE_INPUT_MODE, AppParamConstant.NO);
            } else {
                rglog.debug("商户<{}>是特殊费率商户", mchntNo);
                xmlTreeUtil.setXmlTreeStringValue(McpsTreeNodeConstant.SPECIAL_FEE_INPUT_MODE, AppParamConstant.YES);
            }
        }

        /* Step5：查找BIZ_PROCESS的注解 */
        invokeBizAnnoMethod(BIZ_PROCESS.class);

        /* Step6：业务逻辑处理*/
        return bizProcessCust(request);
    }


    @Override
    protected void afterBizProcess(BizResponse response) throws Exception {
        /* Step1：查找BIZ_AFTER的注解 */
        invokeBizAnnoMethod(BIZ_AFTER.class);
        /* Step2：业务调用后客户化处理 */
        bizAfterCust(response);
    }

    @Override
    protected RiskMonitorInfo getRiskMonitorInfo() {
        RiskMonitorInfo riskMonitorInfo = new RiskMonitorInfo();
        RiskMonitorService riskMonitorService = new RiskMonitorServiceImpl();
        riskMonitorInfo.setTransMonitor(riskMonitorService);
        return riskMonitorInfo;
    }

    /**
     * 业务处理前客户化处理
     *
     * @return 业务处理实体类
     */
    protected abstract Object bizBeforeCust() throws Exception;

    /**
     * 业务逻辑处理客户化处理
     *
     * @param request 业务处理前的返回实体类
     * @return 业务处理结果
     */
    protected abstract BizResponse bizProcessCust(Object request) throws Exception;

    /**
     * 业务处理后逻辑处理
     *
     * @param response 业务调用响应信息
     */
    protected abstract void bizAfterCust(Object response) throws Exception;
}